/* * Copyright (C) 2004 The Concord Consortium, Inc., * 10 Concord Crossing, Concord, MA 01742 * * Web Site: http://www.concord.org * Email: info@concord.org * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * END LICENSE */ /* * Last modification information: * $Revision: 1.26 $ * $Date: 2007-10-17 20:25:27 $ * $Author: scytacki $ * * Licence Information * Copyright 2004 The Concord Consortium */ package org.concord.otrunk.xml; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.StringReader; import java.io.Writer; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JOptionPane; import org.concord.framework.otrunk.OTID; import org.concord.framework.otrunk.OTXMLString; import org.concord.otrunk.OTrunkImpl; import org.concord.otrunk.OTrunkUtil; import org.concord.otrunk.datamodel.BlobResource; import org.concord.otrunk.datamodel.OTDataList; import org.concord.otrunk.datamodel.OTDataMap; import org.concord.otrunk.datamodel.OTDataObject; import org.concord.otrunk.datamodel.OTDatabase; import org.concord.otrunk.datamodel.OTIDFactory; import org.concord.otrunk.datamodel.OTPathID; import org.concord.otrunk.datamodel.OTRelativeID; import org.concord.otrunk.datamodel.OTUUID; import org.concord.otrunk.overlay.OTOverlay; import org.concord.otrunk.xml.XMLReferenceInfo.EnumType; import org.concord.otrunk.xml.XMLReferenceInfo.XmlType; import org.jdom.Comment; import org.jdom.Content; import org.jdom.Document; import org.jdom.Element; import org.jdom.IllegalDataException; import org.jdom.input.JDOMParseException; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; /** * Exporter * Class name and description * * Date created: Nov 17, 2004 * * @author scott<p> * */ public class ExporterJDOM { public static boolean useFullClassNames = false; public static boolean preserveImports = true; public static boolean skipOTrunkWrapping = false; private static Pattern refidPattern = Pattern.compile("(refid=\")([^\"]*)(\")"); private static Pattern viewidPattern = Pattern.compile("(viewid=\")([^\"]*)(\")"); // don't match hrefs with colons those are external links private static Pattern hrefPattern = Pattern.compile("(href=\")([^:\"]*)(\")"); OTDatabase otDb; ArrayList<OTID> writtenIds; ArrayList<String> processedClasses; private ArrayList<OTID> processedIds; private ArrayList<String> duplicateClasses; private HashMap<OTID, ArrayList<OTDataObject>> incomingReferenceMap; private URL contextURL; public static void export(File outputFile, OTDataObject rootObject, OTDatabase db) throws Exception { ExporterJDOM exporter = new ExporterJDOM(); exporter.setContextURL(outputFile.toURL()); Document doc = exporter.buildDocument(rootObject, db); FileOutputStream outputStream = new FileOutputStream(outputFile); writeDocument(doc, outputStream); } /** * Calling this method might be unsafe when the outputstream is updating * a File that already exists. * When the outputstream is created the File could be erased. So if there is an * error while exporting the database, then the original file not be there * as a backup. * * It is better to create an instance, call buildDocument and then create * the outputstream and call writeDocument. * * @param outputStream * @param rootObject * @param db * @throws Exception */ public static void export(OutputStream outputStream, OTDataObject rootObject, OTDatabase db) throws Exception { ExporterJDOM exporter = new ExporterJDOM(); if (db instanceof XMLDatabase && ((XMLDatabase)db).getContextURL() != null) exporter.setContextURL(((XMLDatabase)db).getContextURL()); Document doc = exporter.buildDocument(rootObject, db); writeDocument(doc, outputStream); } public static void exportWithUnixSep(Writer writer, OTDataObject rootObject, OTDatabase db) throws Exception { ExporterJDOM exporter = new ExporterJDOM(); Document doc = exporter.buildDocument(rootObject, db); writeDocument(doc, writer, "\n"); } /** * Calling this method is a unsafe when the writer is writing to a File. * As soon as the writer is created, the file gets erased. So if there is an * error while exporting the database, then the original file not be there * as a backup. * * It is better to create an instance, call buildDocument and then create * the writer and call writeDocument. * * @param writer * @param rootObject * @param db * @throws Exception */ public static void export(Writer writer, OTDataObject rootObject, OTDatabase db) throws Exception { ExporterJDOM exporter = new ExporterJDOM(); Document doc = exporter.buildDocument(rootObject, db); writeDocument(doc, writer); } public static void writeDocument(Document doc, OutputStream outputStream) throws Exception { OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8"); writeDocument(doc, writer); } public static void writeDocument(Document doc, Writer writer) throws Exception { writeDocument(doc, writer, null); } public static void writeDocument(Document doc, Writer writer, String lineSep) throws Exception { Format format = Format.getPrettyFormat(); if(lineSep != null){ format.setLineSeparator(lineSep); } XMLOutputter outputter = new XMLOutputter(format); outputter.output(doc, writer); writer.close(); } public ExporterJDOM() { writtenIds = new ArrayList<OTID>(); processedClasses = new ArrayList<String>(); duplicateClasses = new ArrayList<String>(); processedIds = new ArrayList<OTID>(); incomingReferenceMap = new HashMap<OTID,ArrayList<OTDataObject>>(); } /** * Create a JDOM document which can then be written out with the writeDocument methods. * * @param rootObject * @param db * @return * @throws Exception */ public Document buildDocument(OTDataObject rootObject, OTDatabase db) throws Exception { // If this is a XMLDatabase pre-populate the written classes with the databases existing classes. // This preserves any imported classes that might not have been actually used in the otml file // these imported classes are currently the only way to load in packages, so they need to be preserved. if(preserveImports && db instanceof XMLDatabase){ ArrayList<String> importedClasses = ((XMLDatabase)db).getImportedOTObjectClasses(); processedClasses.addAll(importedClasses); } otDb = db; // Make a first pass through the objects reachable from the rootObject // for each one track its incoming references // and the classnames. Flag duplicate classnames. processObject(rootObject); // Try to create XML. If invalid XML exists AND user chooses to go back // and fix the error, this will throw an exception. We will then NOT // save the file. Element rootObjectElement = null; try { rootObjectElement = exportObject(rootObject, null, null); } catch (Exception e){ // break and pass exception on up throw e; } if (skipOTrunkWrapping) { Document simpleDoc = new Document(rootObjectElement); return simpleDoc; } Element otrunkEl = new Element("otrunk"); OTID dbID = otDb.getDatabaseId(); if(dbID == null){ // create a temporary Id if there isn't one already dbID = OTUUID.createOTUUID(); } otrunkEl.setAttribute("id", dbID.toExternalForm()); Element importsEl = new Element("imports"); otrunkEl.addContent(importsEl); for(int i=0; i<processedClasses.size(); i++) { Element importEl = new Element("import"); importsEl.addContent(importEl); importEl.setAttribute("class", processedClasses.get(i)); } Element objectsEl = new Element("objects"); otrunkEl.addContent(objectsEl); objectsEl.addContent(rootObjectElement); Document doc = new Document(otrunkEl); return doc; } private void processObject(OTDataObject dataObject) throws Exception { OTID id = dataObject.getGlobalId(); // Check if we have processed this object already if(processedIds.contains(id)) { // we've seen this object before just skip it return; } // If we are here then the object hasn't been written out before // and if it has a valid container then we are inside of that container processedIds.add(id); // System.err.println("writting object: " + id); String objectFullClassName = OTrunkImpl.getClassName(dataObject); // If this is the first time we've seen this class then check it if(!processedClasses.contains(objectFullClassName)) { // If not using full class names check for duplicates if(!useFullClassNames){ String objectClassName = getClassName(objectFullClassName); for(String processedFullClassName: processedClasses){ String processedClassName = getClassName(processedFullClassName); if(processedClassName.equals(objectClassName)){ // This is very bad System.err.println("Duplicate Class names found: " + processedFullClassName + " and " + objectFullClassName); System.err.println("This database needs to be saved with useFullClassNames turned on"); if(!duplicateClasses.contains(processedFullClassName)){ duplicateClasses.add(processedFullClassName); } duplicateClasses.add(objectFullClassName); } } } processedClasses.add(objectFullClassName); } String resourceKeys [] = dataObject.getResourceKeys(); for(int i=0; i<resourceKeys.length; i++) { // FIXME: we are ignoring special keys there should way // to identify special keys. String resourceName = resourceKeys[i]; if( resourceName.equals("currentRevision") || resourceName.equals("localId")) { continue; } Object resource = dataObject.getResource(resourceName); if(resource instanceof OTID) { // this is an object reference // recurse // handle the ID, check to see if it is in our database or not // if it is in our database then we should recurse processReference(dataObject, (OTID)resource); } else if(resource instanceof OTDataList) { OTDataList list = (OTDataList)resource; for(int j=0;j<list.size(); j++) { Object listElement = list.get(j); processCollectionItem(dataObject, listElement); } } else if(resource instanceof OTDataMap) { OTDataMap map = (OTDataMap)resource; String [] mapKeys = map.getKeys(); for(int j=0; j<mapKeys.length; j++) { // We currently support special maps that use object ids as keys. // The code below handles this situation. This is code is a bit dangerous because // we might incorrectly identify a key as an id, but there isn't a better solution // at the moment. try { OTID otid = OTIDFactory.createOTID(mapKeys[j]); if(otid != null){ processReference(dataObject, otid); } } catch (Throwable t) { // silently ignore ids which can't be parsed correctly } Object mapValue = map.get(mapKeys[j]); if(mapValue != null) { processCollectionItem(dataObject, mapValue); } } } else if(resource instanceof OTXMLString) { // handle xml strings because they might have references to // objects in them. // first search for any id pattern after find it // record the reference, and possibly substitute it // if it is a local_id reference processXMLString(refidPattern, dataObject, (OTXMLString) resource); processXMLString(hrefPattern, dataObject, (OTXMLString) resource); processXMLString(viewidPattern, dataObject, (OTXMLString) resource); } } } private void processXMLString(Pattern pattern, OTDataObject dataObject, OTXMLString xmlString) throws Exception { Matcher m = pattern.matcher(xmlString.getContent()); while(m.find()) { String otidStr = m.group(2); OTID otid = OTIDFactory.createOTID(otidStr); processReference(dataObject, otid); } } private String exportXMLString(Pattern pattern, String xmlString) { Matcher m = pattern.matcher(xmlString); StringBuffer parsed = new StringBuffer(); while(m.find()) { String otidStr = m.group(2); OTID otid = OTIDFactory.createOTID(otidStr); String convertedId = convertId(otid); String escapedConvertedId = OTrunkUtil.escapeReplacement(convertedId); m.appendReplacement(parsed, "$1" + escapedConvertedId + "$3"); } m.appendTail(parsed); return parsed.toString(); } private void processCollectionItem(OTDataObject parent, Object listElement) throws Exception { if(listElement instanceof OTID) { // this is an object reference, recurse processReference(parent, (OTID)listElement); } else if(listElement instanceof OTDataList || listElement instanceof OTDataMap) { System.err.println("nested collections are illegal"); } } private void processReference(OTDataObject parent, OTID id) throws Exception { OTDataObject childObject = otDb.getOTDataObject(parent, id); if(childObject == null) { // our db doesn't contain this object, so we don't need // to do anything with it. // FIXME: This should be a little more careful. The list of // databases we require should be saved. So then we can check // if this external object will be resolvable on loading. return; } else { // record that this parent object is referencing this object. ArrayList<OTDataObject> refList = incomingReferenceMap.get(id); if(refList == null){ refList = new ArrayList<OTDataObject>(); incomingReferenceMap.put(id, refList); } refList.add(parent); processObject(childObject); } } public Element exportCollectionItem(OTDataObject parentDataObj, Object item, String parentResourceName) throws Exception { if(item == null) { return new Element("null"); } else if(item instanceof OTID) { // this is an object reference // recurse return exportID(parentDataObj, (OTID)item, parentResourceName); } else if(item instanceof OTDataList || item instanceof OTDataMap) { System.err.println("nested collections are illegal"); return null; } else { // this is a literal reference in a list or map so we need the type String type = null; type = TypeService.getDataPrimitiveType(item.getClass()); if(type == null){ String msg = "ExporterJDOM#exportCollectionItem: "; msg += "Unknown list item type: " + item.getClass(); msg += " parentResourceName=" + parentResourceName; System.err.println(msg); return null; } Element typeEl = new Element(type); String itemString; if(!(item instanceof BlobResource)) { itemString = item.toString(); } else { BlobResource blob = (BlobResource)item; if(blob.getBlobURL() != null){ itemString = blob.getBlobURL().toExternalForm(); } else { itemString = BlobTypeHandler.base64(blob.getBytes()); } } typeEl.setText(itemString); return typeEl; } } public Element exportID(OTDataObject parent, OTID id, String parentResourceName) throws Exception { OTDataObject childObject = otDb.getOTDataObject(parent, id); if(childObject == null) { // our db doesn't contain this object // so write out a reference to it and hope that we can find // it when we are loaded in. // FIXME: This should be a little more careful. The list of // databases we require should be saved. So then we can check // if this external object will be resolvable on loading. return exportObjectReference(id); } else { return exportObject(childObject, parent, parentResourceName); } } public Element exportObjectReference(OTID id) { Element objectEl = new Element("object"); String convertedId = convertId(id); objectEl.setAttribute("refid", convertedId); return objectEl; } /** * This will return true of the passed in a id is a localId in the current * database. * * @param id * @return */ public boolean isLocalId(OTID id) { if(id instanceof OTRelativeID){ OTRelativeID relId = (OTRelativeID) id; OTID relRelId = relId.getRelativeId(); String externalRelRelId = relRelId.toExternalForm(); if(relId.getRootId().equals(otDb.getDatabaseId()) && relRelId instanceof OTPathID && externalRelRelId.startsWith("/") && // This last condition is a hack so path ids which start with an expanded local_id // don't get written out as local_is (externalRelRelId.substring(1).indexOf('/') == -1)){ return true; } } return false; } /** * Check if this id is actually a local id reference. If it is then * return the ${xxx} notation used in the otml files. * @param id * @return */ public String convertId(OTID id) { if(isLocalId(id)){ OTRelativeID relId = (OTRelativeID) id; OTID relRelId = relId.getRelativeId(); return "${" + relRelId.toExternalForm().substring(1) + "}"; } else { return id.toExternalForm(); } } public Element exportObject(OTDataObject dataObj, OTDataObject parent, String parentResourceName) throws Exception { OTID id = dataObj.getGlobalId(); // Check if we should write out a reference if(shouldWriteReference(dataObj, parent, parentResourceName)) { // never write an object reference if (parentResourceName.startsWith(OTOverlay.NON_DELTA_OBJECTS_ATTRIBUTE + "[")) { // System.out.println("skipping writing object reference"); return null; } return exportObjectReference(id); } // If we are here then the object hasn't been written out before // and if it has a valid container then we are inside of that container writtenIds.add(id); // System.err.println("writing object: " + id); String objectFullClassName = OTrunkImpl.getClassName(dataObj); String objectElementName = getObjectElementName(objectFullClassName); Element objectEl = new Element(objectElementName); XMLDataObject xmlDO = null; if(dataObj instanceof XMLDataObject){ xmlDO = (XMLDataObject) dataObj; } if(xmlDO != null && xmlDO.getLocalId() != null){ // FIXME this is dangerous if the object is being copied from one db // to another, the references in this export should be checked to see // if they use the local_id or its global as a reference. objectEl.setAttribute("local_id", xmlDO.getLocalId()); } else { // check to see if this object has any references that // are not containment references. // there is only one containment reference and every object // should have one (with the exception of the root object) // so if there is more than one incoming reference than this object // needs an id. // We should also check to see if the objects id has been explicitly set // in that case it should be written out anyhow. I'm not sure how to // do that yet. ArrayList<OTDataObject> incomingReferences = incomingReferenceMap.get(id); if((xmlDO != null && xmlDO.isPreserveUUID() && id instanceof OTUUID) || (incomingReferences != null && incomingReferences.size() > 1)){ objectEl.setAttribute("id", id.toExternalForm()); } } String resourceKeys [] = dataObj.getResourceKeys(); ArrayList<String> nullResources = new ArrayList<String>(); for(int i=0; i<resourceKeys.length; i++) { // FIXME: we are ignoring special keys there should way // to identify special keys. String resourceName = resourceKeys[i]; if( resourceName.equals("currentRevision") || resourceName.equals("localId")) { continue; } Object resource = dataObj.getResource(resourceName); if(resource instanceof OTID) { // this is an object reference // recurse Element objectIDEl = exportID(dataObj, (OTID)resource, resourceName); writeResourceElement(dataObj, objectEl, resourceName, objectIDEl); } else if(resource instanceof OTDataList) { OTDataList list = (OTDataList)resource; if(list.size() == 0){ continue; } ArrayList<Content> content = new ArrayList<Content>(); for(int j=0;j<list.size(); j++) { Object listElement = list.get(j); if(list instanceof XMLDataList){ XMLReferenceInfo info = ((XMLDataList)list).getReferenceInfo(j); if(info != null && info.comment != null){ content.add(new Comment(info.comment)); } } Element collectionEl = exportCollectionItem(dataObj, listElement, resourceName + "[" + j + "]"); if(collectionEl != null){ content.add(collectionEl); } } writeResourceElement(dataObj, objectEl, resourceName, content); } else if(resource instanceof OTDataMap) { OTDataMap map = (OTDataMap)resource; String [] mapKeys = map.getKeys(); ArrayList<Content> content = new ArrayList<Content>(); for(int j=0; j<mapKeys.length; j++) { Element entryEl = new Element("entry"); content.add(entryEl); // We currently support special maps that use object ids as keys. Inorder to preserve the otml // correctly we need to check if the mapKey is an id and if so then it should be converted. // When it is converted it will be turned into a ${} local_id reference. Because we don't know // if the keys of the map are supposed to be ids or just plain strings. We have to be careful to // not screw up regular strings that look like ids. // So we only modify the key if it is a local_id in this database. String exportedKey = mapKeys[j]; try { OTID otid = OTIDFactory.createOTID(mapKeys[j]); if(otid != null){ if(isLocalId(otid)){ exportedKey = convertId(otid); } } } catch (Throwable t) { // silently ignore ids which can't be parsed correctly } entryEl.setAttribute("key", exportedKey); Object mapValue = map.get(mapKeys[j]); Element collectionEl = exportCollectionItem(dataObj, mapValue, resourceName + "['" + exportedKey + "']"); entryEl.addContent(collectionEl); } writeResourceElement(dataObj, objectEl, resourceName, content); } else if(resource instanceof BlobResource){ BlobResource blob = (BlobResource) resource; URL blobUrl = blob.getBlobURL(); String blobString = null; XmlType defaultType = XmlType.ELEMENT; if(blobUrl != null){ if(contextURL != null){ blobString = URLUtil.getRelativeURL(contextURL, blobUrl); } else { blobString = blobUrl.toString(); } defaultType = XmlType.ATTRIBUTE; } else { blobString = BlobTypeHandler.base64(blob.getBytes()); } writeResource(dataObj, objectEl, resourceName, blobString, defaultType); } else if(resource == null){ if(xmlDO.getSaveNulls()){ nullResources.add(resourceName); } else { System.err.println("Got null resource value"); } } else if(resource instanceof Integer || resource instanceof Float || resource instanceof Byte || resource instanceof Short || resource instanceof Boolean) { String primitiveString = resource.toString(); writeResource(dataObj, objectEl, resourceName, primitiveString, XmlType.ATTRIBUTE); } else if(resource instanceof OTXMLString) { // The xml string is wrapped with a fake root element // and loaded as a JDOM document // and if it doesn't fail then clone the contents of the fake root // element and add it to the resourceElement // FIXME if it does fail then write it out as CDATA // FIXME we should process any references String xmlString = ((OTXMLString)resource).getContent(); xmlString = exportXMLString(refidPattern, xmlString); xmlString = exportXMLString(viewidPattern, xmlString); xmlString = exportXMLString(hrefPattern, xmlString); String originalString = xmlString.trim(); SAXBuilder builder = new SAXBuilder(); xmlString = "<root>" + originalString + "</root>"; StringReader reader = new StringReader(xmlString); try{ Document xmlStringDoc = builder.build(reader, resourceName); Element rootXMLStringEl = xmlStringDoc.getRootElement(); writeResourceElement(dataObj, objectEl, resourceName, rootXMLStringEl.cloneContent()); } catch(JDOMParseException e){ System.err.println("User-generated XML error: "+e.getCause()); System.err.println("Invalid xmlString"); System.err.println("-----"); System.err.println(xmlString); System.err.println("-----"); // show warning, and let user choose to go back and edit String warning = "Warning: There is an HTML error in your text.\n " + e.getCause(); Object[] options = {"Save anyway and ignore the errors", "I will go back and fix the error"}; boolean saveAnyway = JOptionPane.showOptionDialog(null, warning, "HTML error", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[1]) == 0; if (!saveAnyway){ throw new Exception("JDOMParseException caught. User will edit invalid XML"); } else { writeResource(dataObj, objectEl, resourceName, XMLStringTypeHandler.INVALID_PREFIX + originalString, XmlType.ELEMENT); } e.printStackTrace(); } } else if(resource instanceof String) { writeResource(dataObj, objectEl, resourceName, (String) resource, XmlType.ATTRIBUTE); } else if(resource instanceof Enum) { EnumType enumType = EnumType.STRING; if(dataObj instanceof XMLDataObject){ XMLDataObject xmlObj = (XMLDataObject)dataObj; XMLReferenceInfo resInfo = xmlObj.getReferenceInfo(resourceName); if(resInfo != null){ enumType = resInfo.enumType; } } String str = null; switch(enumType){ case INT: str = Integer.toString(((Enum)resource).ordinal()); break; case STRING: str = ((Enum)resource).name(); break; } writeResource(dataObj, objectEl, resourceName, str, XmlType.ATTRIBUTE); } else { String primitiveString = resource.toString(); writeResource(dataObj, objectEl, resourceName, primitiveString, XmlType.ATTRIBUTE); } } if(nullResources.size() > 0){ String unsetList = ""; for(int i=0; i<nullResources.size(); i++){ unsetList += nullResources.get(i) + " "; } unsetList = unsetList.trim(); objectEl.setAttribute("unset", unsetList); } return objectEl; } public String getObjectElementName(String objectClass) { /* I don't know why this is being done, but it was here before * so I'll leave it for now. */ if(objectClass == null) { return "object"; } if(!useFullClassNames){ if(duplicateClasses.contains(objectClass)){ return objectClass; } return getClassName(objectClass); } return objectClass; } protected boolean shouldWriteReference(OTDataObject dataObj, OTDataObject parent, String parentResourceName) { OTID id = dataObj.getGlobalId(); // Check if we have written out this object already // if so then just write a reference so the object isn't written twice if(writtenIds.contains(id)) { return true; } // check if this object has valid container if(!(dataObj instanceof XMLDataObject)){ return false; } XMLDataObject xmlDO = (XMLDataObject) dataObj; XMLDataObject container = xmlDO.getContainer(); if(container == null || !processedIds.contains(container.getGlobalId())){ // the container isn't set, or the container isn't going to be written out // by this exporter return false; } // this is a valid container // does it have the current object in this spot String containerResourceName = xmlDO.getContainerResourceKey(); if(parent == container && parentResourceName.equals(containerResourceName)){ // our parent is the container and our parent property is the same as the container property // so the actual object should be written here return false; } // this isn't the parent, or it isn't the right resource in the parent Object containedValue = container.getResourceWithSuffix(containerResourceName); // the containedValue can be null if it doesn't exist any more if(id.equals(containedValue)){ // the container still contains the correct value // so just write a reference here return true; } // The container doesn't reference this object anymore, or at least not in the // same spot as before, so the full object needs to be written return false; } public static String getClassName(String fullClassName) { return fullClassName.substring(fullClassName.lastIndexOf('.')+1); } public static void writeResourceElement(OTDataObject dataObj, Element objectEl, String resourceName, Object content) { if(content == null){ return; } XMLReferenceInfo resInfo = null; if(dataObj instanceof XMLDataObject){ XMLDataObject xmlObj = (XMLDataObject)dataObj; resInfo = xmlObj.getReferenceInfo(resourceName); } if(resInfo != null){ if(resInfo.comment != null){ Comment comment = new Comment(resInfo.comment); objectEl.addContent(comment); } } Element resourceEl = new Element(resourceName); objectEl.addContent(resourceEl); if(content instanceof Element){ resourceEl.setContent((Element) content); } else if(content instanceof Collection){ resourceEl.setContent((Collection<?>) content); } } public static void writeResource(OTDataObject dataObj, Element objectEl, String resourceName, String resourceValue, XmlType defaultType) { XMLReferenceInfo resInfo = null; if(dataObj instanceof XMLDataObject){ XMLDataObject xmlObj = (XMLDataObject)dataObj; resInfo = xmlObj.getReferenceInfo(resourceName); } boolean writeElement = defaultType == XmlType.ELEMENT; if(resInfo != null){ writeElement = resInfo.xmlType == XmlType.ELEMENT; if(resInfo.comment != null){ Comment comment = new Comment(resInfo.comment); objectEl.addContent(comment); } } else if(resourceValue.indexOf('\n') > 0){ writeElement = true; } try { if(writeElement){ Element resourceEl = new Element(resourceName); objectEl.addContent(resourceEl); resourceEl.setText(resourceValue); } else { objectEl.setAttribute(resourceName, resourceValue); } } catch (IllegalDataException e) { System.err.println("Tried to write: \"" + resourceValue + "\" into \"" + resourceName +"\""); System.err.println("skiping this property"); } } public URL getContextURL() { return contextURL; } public void setContextURL(URL contextURL) { this.contextURL = contextURL; } }